/*
 * Copyright (C) 2008 by Xume.
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package org.xume.critann.jpa.criteria;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.persistence.EntityManager;
import javax.persistence.FlushModeType;
import javax.persistence.Query;

import org.xume.critann.jpa.criteria.criterion.Criterion;
import org.xume.critann.jpa.criteria.projection.Projection;

/**
 * $Id: Criteria.java 18 2008-10-13 19:31:32Z johan.siebens $
 * 
 * @author Johan Siebens
 */
public class Criteria {

	private List<Criterion> criterions = new ArrayList<Criterion>();

	private Class<?> entityClass;

	private EntityManager entityManager;

	private Integer firstResult;

	private FlushModeType flushMode;

	private Map<String, Object> hints = new HashMap<String, Object>();

	private Integer maxResults;

	private List<Order> orders = new ArrayList<Order>();

	private Projection projection;

	public Criteria(Class<?> entityClass, EntityManager entityManager) {
		this.entityClass = entityClass;
		this.entityManager = entityManager;
	}

	public Criteria add(Criterion criterion) {
		this.criterions.add(criterion);
		return this;
	}

	public Criteria addOrder(Order order) {
		this.orders.add(order);
		return this;
	}

	public List<?> list() {
		return buildJpaQuery().getResultList();
	}

	public void setFirstResult(int firstResult) {
		this.firstResult = firstResult;
	}

	public void setFlushMode(FlushModeType flushMode) {
		this.flushMode = flushMode;
	}

	public void setHint(String name, Object value) {
		this.hints.put(name, value);
	}

	public void setMaxResults(int maxResults) {
		this.maxResults = maxResults;
	}

	public void setProjection(Projection projection) {
		this.projection = projection;
	}

	public Object uniqueResult() {
		return buildJpaQuery().getSingleResult();
	}

	private void applyCriterions(QueryContext context, StringBuilder qlString) {
		if (!criterions.isEmpty()) {
			qlString.append(" where ");
			for (Iterator<Criterion> iterator = criterions.iterator(); iterator.hasNext();) {
				Criterion criterion = iterator.next();
				qlString.append(criterion.toQueryFragment(context));

				if (iterator.hasNext()) {
					qlString.append(" and ");
				}
			}
		}
	}

	private void applyOrders(QueryContext context, StringBuilder qlString) {
		if (!orders.isEmpty()) {
			qlString.append(" order by ");
			for (Iterator<Order> iterator = orders.iterator(); iterator.hasNext();) {
				Order order = iterator.next();
				qlString.append(order.toQueryFragment(context));

				if (iterator.hasNext()) {
					qlString.append(", ");
				}
			}
		}
	}

	private Query buildJpaQuery() {
		QueryContext context = new QueryContext();
		String alias = context.getEntityAlias();

		String selectClause = alias;

		if (projection != null) {
			selectClause = projection.toQueryFragment(context);
		}

		StringBuilder qlString = new StringBuilder("select " + selectClause + " from " + entityClass.getSimpleName() + " as " + alias);

		applyCriterions(context, qlString);
		applyOrders(context, qlString);

		Query query = entityManager.createQuery(qlString.toString());

		for (Entry<String, Object> entry : hints.entrySet()) {
			query.setHint(entry.getKey(), entry.getValue());
		}

		if (firstResult != null) {
			query.setFirstResult(firstResult);
		}

		if (maxResults != null) {
			query.setMaxResults(maxResults);
		}

		if (flushMode != null) {
			query.setFlushMode(flushMode);
		}

		for (Entry<String, Object> entry : context.getParameters().entrySet()) {
			query.setParameter(entry.getKey(), entry.getValue());
		}

		return query;
	}
}
